package com.tiny.jpa.config;

import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.hibernate.boot.Metadata;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.mapping.*;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;

import java.lang.reflect.Field;
import java.util.Iterator;


/**
 * 自定义的处理字段注释的执行器
 * @author honglin.li
 * @date 2022/6/27
 */
public class CustomIntegrator implements Integrator {

    public static final CustomIntegrator INSTANCE = new CustomIntegrator();

    public CustomIntegrator() {

        super();
    }

    /**
     * Perform comment integration.
     * @param metadata        The "compiled" representation of the mapping information
     * @param sessionFactory  The session factory being created
     * @param serviceRegistry The session factory's service registry
     */
    @Override
    public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {

        processComment(metadata);

    }

    /**
     * Not used.
     * @param sessionFactoryImplementor     The session factory being closed.
     * @param sessionFactoryServiceRegistry That session factory's service registry
     */
    @Override
    public void disintegrate(SessionFactoryImplementor sessionFactoryImplementor, SessionFactoryServiceRegistry sessionFactoryServiceRegistry) {

    }

    
    /**
     * 自定义处理注释信息的方法
     * 根据类上的swagger注解(@ApiModel、@ApiModelProperty),对JPA创建的表和字段,设置注释信息
     * @param metadata
     */
    private void processComment(Metadata metadata) {
        metadata.getEntityBindings().forEach(persistentClass -> {
            Class<?> clz = persistentClass.getMappedClass();
            System.out.println(clz.getName());

            // 如果当前实体类被 @ApiModel 注解修饰，则从中获取注释信息
            if (clz.isAnnotationPresent(ApiModel.class)) {
                ApiModel comment = clz.getAnnotation(ApiModel.class);
                persistentClass.getTable().setComment(comment.value());
            }

            // 获取实体类中用于定义标识符的属性，也就是在 @Id 注解中标记的属性
            Property identifierProperty = persistentClass.getIdentifierProperty();
            Opt.of(identifierProperty).ifPresent(x -> fieldComment(persistentClass, clz, x));

            //处理实体类中包含的字段
            Iterator<Property> iterator = persistentClass.getPropertyIterator();
            while (iterator.hasNext()) {
                Property property = iterator.next();
                handleNestedProperty(persistentClass, clz, property);
            }
        });
    }

    /**
     * 递归解析实体类涉及的属性信息,并设置自定义的注释
     * @param parent
     * @param parentClass
     * @param property
     */
    private void handleNestedProperty(PersistentClass parent, Class parentClass, Property property) {
        Value value = property.getValue();

        //如果是Component,表名该属性是嵌入式的类。需要解析嵌入属性
        if (value instanceof Component) {
            //嵌入式的类,也就是使用了@Embedded修饰的字段属性,配合@Embeddable注解修饰的类一同组成
            Component component = (Component) value;
            
            Class componentClass = component.getComponentClass();

            Iterator<Property> subPropertyIterator = component.getPropertyIterator();
            
            while (subPropertyIterator.hasNext()) {
                Property subProperty = subPropertyIterator.next();
                //继续递归
                handleNestedProperty(parent, componentClass, subProperty);
            }
            
        } else {
            //非嵌入式属性,可直接进入注释设置环节
            fieldComment(parent, parentClass, property);
        }
    }

    /**
     * 针对字段的注释信息设置
     * @param persistentClass
     * @param property
     */
    private void fieldComment(PersistentClass persistentClass, Class clz, Property property) {

        Field field = ReflectUtil.getField(clz, property.getName());

        boolean withComment = field.isAnnotationPresent(ApiModelProperty.class);

        if (!withComment) {
            return;
        }

        //字段入库时对应的字段名
        String tableFieldName = StrUtil.toUnderlineCase(property.getName());

        //如果字段上配置了ApiModelProperty注解,则将该注解的值设置为数据库字段的注释信息
        String comment = field.getAnnotation(ApiModelProperty.class).value();

        Column column = persistentClass.getTable().getColumn(new Column(tableFieldName));

        Opt.ofNullable(column).ifPresent(c -> c.setComment(comment));
    }



}
